<?php
namespace Swiftx\Component\Orm;
use ReflectionClass;
use Swiftx\Component\Orm\Exceptions\Error;
use Swiftx\Component\Orm\Reflection\Schema;
use Swiftx\Interfaces\Ioc\Container;
use Swiftx\Interfaces\Orm\Builder;
use Swiftx\Interfaces\Orm\Entity;
use Swiftx\Interfaces\Orm\Manager as ManagerInterface;
use Swiftx\Interfaces\Orm\Factory as FactoryInterface;
use ErrorException;
use PDOException;
use PDO;
use Swiftx\Component\Xml\Document;
use Swiftx\Interfaces\Orm\Query as InterfaceQuery;
use Swiftx\Component\Orm\Statement\Query;

/**
 * 创建一个Manager对象
 * @package Swiftx\Interfaces\Orm
 */
class Manager implements ManagerInterface{

    /**
     * 数据库连接句柄
     * @var PDO
     */
    protected $connect;

    /**
     * 注入容器
     * @var Container
     * @auto-injection
     */
    protected $container;

    /**
     * 数据库结构映射
     * @var Schema[]
     */
    protected $schemas = [];

    /**
     * 实体更新缓存
     * @var Entity[]
     */
    protected $saveCache = [];

    /**
     * 实体缓存
     * @var Entity[]
     */
    protected $entityCache = [];

    /**
     * 运行时目录
     * @var string
     */
    protected $runtimeDir;

    /**
     * 连接数据库系统
     * @param string $type
     * @param string $host
     * @param string $name
     * @param string|null $username
     * @param string|null $password
     *
     * @throws ErrorException
     */
    public function connection(string $type, string $host, string $name, string $username = null, string $password = null){
        try{
            $dsn = $type.':host='.$host.';dbname='.$name;
            $this->connect = new PDO($dsn,$username,$password);
        } catch(PDOException $e){
            throw new ErrorException("数据库连接失败:".$e->getMessage());
        }
    }

    /**
     * 设置运行时目录
     * @param string $path
     */
    public function setRuntimeDir(string $path){
        $this->runtimeDir = $path;
    }

    /**
     * 获取Schema对象
     * @param string $class
     * @return Schema
     * @throws ErrorException
     */
    protected function getSchema(string $class):Schema{
        if(isset($this->schemas[$class]))
            return $this->schemas[$class];
        throw new ErrorException($class.'实体未注册！');
    }

    /**
     * 构建数据库结构
     * @param string $class
     * @return Schema
     * @throws ErrorException
     */
    protected function buildSchema(string $class):Schema{
        // 检查目标类是否存在
        if(!class_exists($class))
            throw new ErrorException("类不存在:".$class);
        // 检查目标类是否实现接口
        $reflect = new ReflectionClass($class);
        if(!$reflect->implementsInterface(Entity::class))
            throw new ErrorException($class.' 未实现'.Entity::class.'接口');
        // 构造并返回模型结构实例
        $schema = new Schema($class, $this->connect);
        $this->container->render($schema);
        return $schema;
    }

    /**
     * 查看表名称
     * @param string $class
     * @return string
     */
    public function translateTableName(string $class):string {
        return $this->getSchema($class)->getTableName();
    }

    /**
     * 查看表字段
     * @param string $class
     * @param string $prototype
     * @return string
     */
    public function translateTableField(string $class, string $prototype):string {
        return $this->getSchema($class)->getTableFieldName($prototype);
    }

    /**
     * 从数组数据构建对象
     * @param string $class
     * @param array $data
     * @return Entity
     */
    public function buildEntity(string $class, array $data):Entity{
        return $this->getSchema($class)->buildObject($data);
    }

    /**
     * 构建数据库结构
     */
    public function buildSchemas(){
        foreach ($this->schemas as $schema) {
            $table = $schema->createTable();
            $table->drop();
            $table->create();
        }
    }

    /**
     * 设置映射数据表
     * @param string $interface
     * @param string $table
     * @throws ErrorException
     */
    public function setMappingTable(string $interface, string $table){
        $this->getSchema($interface)->setTable($table);
    }

    /**
     * 解析类名
     * @param string $class
     * @param string $namespace
     * @param string[] $uses
     * @return string
     */
    protected function analysisClassName(string $class, string $namespace, array $uses=[]){
        if($class[0] == '\\') return $class;
        $name = explode('\\', $class);
        $prefix = $name[0];
        if(!isset($uses[$prefix]))
            return $namespace.'\\'.$class;
        $prefix = $uses[$prefix];
        unset($name[0]);
        if(empty($name)) return $prefix;
        return $prefix.'\\'.implode('\\',$name);
    }

    /**
     * 注册映射配置文件
     * @param string $path
     * @throws ErrorException
     */
    public function registerMappingConfig(string $path){
        // 加载配置文件
        $config = new Document();
        $config->loadFile($path);
        // 解析命名空间
        $namespace = $config->getAttribute('namespace')->getValue();
        $uses = [];
        foreach ($config->getNodeByTagName('use') as $use){
            $value = (string)$use;
            if($use->hasAttribute('name')) {
                $name = $use->getAttribute('name')->getName();
            }else {
                $name = explode('\\', $value);
                $name = end($name);
            }
            $uses[$name] = $value;
        }
        // 解析实体配置段
        foreach ($config->getNodeByTagName('entity') as $entity){
            // 检查class属性配置项
            if(!$entity->hasAttribute('class'))
                throw new ErrorException($path.'存在未配置class的entity配置项');
            // 构造节点映射的模型类
            $className = $entity->getAttribute('class')->getValue();
            $className = $this->analysisClassName($className,$namespace,$uses);
            $schema = $this->buildSchema($className);
            $this->schemas[$className] = $schema;
            // 设置配置属性
            foreach ($entity->getAttributes() as $attribute){
                if($attribute->getName() == 'class') continue;
                $method = 'set'.ucfirst($attribute->getName());
                if(!method_exists($schema, $method))
                    throw new Error('无效的配置项：'.$attribute->getName());
                call_user_func([$schema, $method], $attribute->getValue());
            }
            foreach ($entity->children('query') as $query){
                // 检查name属性配置项
                if(!$query->hasAttribute('name'))
                    throw new Error($path.'存在未配置name的query配置项');
                // 构造节点映射的模型类
                if(!$query->hasAttribute('class')){
                    $queryObject = new Query($this, $namespace, $uses);
                } else {
                    $queryObject = $query->getAttribute('class')->getValue();
                    $queryObject = $this->analysisClassName($queryObject,$namespace,$uses);
                    /** @var InterfaceQuery $queryObject */
                    $queryObject = new $queryObject($this, $namespace, $uses);
                }
                $this->container->render($queryObject);
                // 设置配置属性
                foreach ($query->getAttributes() as $attribute){
                    if($attribute->getName() == 'class') continue;
                    $method = 'set'.ucfirst($attribute->getName());
                    if(!method_exists($queryObject, $method))
                        throw new Error('无效的配置项：'.$attribute->getName());
                    call_user_func([$queryObject, $method], $attribute->getValue());
                }
                // 注册查询器
                $queryObject->setSql($query->__toString());
                $queryObject->setConnect($this->connect);
                $queryObject->setEntity($className);
                $queryObject->setRuntimeDir($this->runtimeDir.'/sql');
                $schema->registerQuery($queryObject);
            }
        }
    }

    /**
     * 获取实体
     * @param string $entity
     * @param array ...$keys
     * @return Entity
     * @throws ErrorException
     */
    public function fetch(string $entity, ... $keys): ?Entity{
        $schema = $this->getSchema($entity);
        $function = [$schema, 'fetchByPrimary'];
        return call_user_func_array($function, $keys);
    }

    /**
     * 获取查询器
     * @param string $entity
     * @param string $name
     * @return InterfaceQuery
     */
    public function query(string $entity, string $name):InterfaceQuery{
        return $this->getSchema($entity)->getQuery($name);
    }

    /**
     * 获取工厂基础实例
     * @param string $entity
     * @return FactoryInterface
     */
    public function simpleFactory(string $entity):FactoryInterface{
        /** @var FactoryInterface $factory */
        $factory = $this->container->fetch(FactoryInterface::class);
        $factory->setEntityClass($entity);
        return $factory;
    }

    /**
     * 获取构造器实例
     * @param string $entity
     * @return Builder
     */
    public function builder(string $entity):Builder{
        /** @var Builder $builder */
        $builder = $this->container->fetch(Builder::class);
        $builder->setEntity($entity);
        $builder->setConnect($this->connect);
        $builder->setManager($this);
        return $builder;
    }

    /**
     * 存储实体
     * @param Entity $object
     */
    public function save(Entity $object){
        $this->saveCache[] = $object;
    }

    /**
     * 刷新生效
     * @param bool $transaction
     */
    public function flush(bool $transaction=false){
        // 消除重复项目
        $this->saveCache = array_unique($this->saveCache, SORT_REGULAR);
        // 处理存储队列
        foreach($this->saveCache as $entity){
            $entity_class = get_class($entity);
            $schema = $this->getSchema($entity_class);
            $schema->saveEntity($entity);
        }
        // 清理缓存队列
        $this->saveCache = [];
    }

}